package com.web.wrapper.hood;

import javax.servlet.ServletContext;
import org.apache.log4j.Logger;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.ceph.rados.IoCTX;
import com.ceph.rados.exceptions.RadosException;
import com.ceph.rados.exceptions.RadosNotFoundException;
import com.ceph.rbd.Rbd;
import com.ceph.rbd.RbdImage;
import com.alibaba.fastjson.JSONObject;
import com.web.exception.CephWebException;
import com.web.util.util;
import static com.web.util.util.nullCheck;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.regex.Pattern;

/**
 * create a ceph rbd for a different pool
 * @author dell2
 */
public class CephRBDAccessor {
	private CephRados rados ;
	private Rbd executor ;
	private Logger logger = Logger.getLogger( getClass() ) ;
	private IoCTX ioCtx ;
	private String pool ;
	
	public CephRBDAccessor(String pool) {
		this.pool = pool ;
	}
	
	@SuppressWarnings("static-access")
	public void connectOnly(ServletContext sc) throws Exception{
		util.setRados(sc, this);
		int tryTimes = 3 ;
		while( tryTimes >=0 ){
			try {
				logger.debug( "pool name is "+pool );
				ioCtx= rados.getRados().ioCtxCreate( pool);
				executor= new Rbd( ioCtx );
				break ;
			} catch (RadosException e) {
				if( e instanceof RadosNotFoundException ){
					 throw new CephWebException("there is no pool existed,pool:"+pool) ;
				}
				if( --tryTimes >= 0){
					logger.debug("counter:"+tryTimes);
					Thread.currentThread().sleep( 500 );
					continue;
				}
				logger.error("Can not instance rbd",e);
				throw new CephWebException("Can not create io context") ;
			}
		}	
	}
	
	/**
	 * create a ioCtx first ,then the rbd can be used
	 * if there is no pool ,create it
	 */
	@SuppressWarnings("static-access")
	public void connect(ServletContext sc) throws Exception{
		if( null == rados ){
			executor = null ;
			rados = WebApplicationContextUtils.getRequiredWebApplicationContext(sc).getBean(CephRados.class);
		}
		int tryTimes = 3 ;
		while( tryTimes >=0 ){
			try {
				logger.debug( "pool name is "+pool );
				ioCtx= rados.getRados().ioCtxCreate( pool);
				executor= new Rbd( ioCtx );
				break ;
			} catch (RadosException e) {
				if( e instanceof RadosNotFoundException 
						&& null!= e.getMessage()
						&&e.getMessage().contains("Failed to create the IoCTX for pool") ){
					//com.ceph.rados.exceptions.RadosNotFoundException: Failed to create the IoCTX for pool foo
					logger.warn("there is no poo exist,so create it "+pool);
					rados.getRados().poolCreate( pool );
				}
				if( --tryTimes >= 0){
					logger.debug("counter:"+tryTimes);
					Thread.currentThread().sleep( 500 );
					continue;
				}
				logger.error("Can not instance rbd",e);
				throw new CephWebException("Can not create io context") ;
			}
		}
		
	}
	
	public void createPool(ServletContext sc) throws Exception{
		try {
			util.setRados(sc, this);
			rados.getRados().poolCreate( pool );
		} catch (Exception e) {
			logger.error("Can not create pool of "+pool ,e);
			throw new IllegalArgumentException("can not create pool of "+pool+ e.getMessage());
		}
	}
	
	public String[] listPool(ServletContext sc) throws Exception {
		util.setRados(sc, this);
		return this.rados.getRados().poolList();
	}
	
	public void removePool(ServletContext sc) throws Exception{
		try {
			util.setRados(sc, this);
			rados.getRados().poolDelete( pool );
		} catch (Exception e) {
			logger.error("Can not create pool of "+pool ,e);
			throw new IllegalArgumentException("can not create pool of "+pool);
		}
	}
	
	/**
	 * create a rbd v2 image,default features bit will be assign 3
	 * default features refer http://www.zphj1987.com/2016/06/07/rbd%E6%97%A0%E6%B3%95map-rbd-feature-disable/
	 * @param name The size of the new image in bytes
	 * @param size The size of the new image in bytes
	 * @throws Exception
	 */
	public void createImg(String name,long size) throws  Exception{
		nullCheck(executor);
		executor.create(name,size,(long)3) ;
	}
	
	public String[] listImg() throws Exception {
	    util.nullCheck(this.executor);
	    String[] arr = this.executor.list();
	    if (null == arr) {
	    	arr = new String[0];
	    }
	    return arr;
	}
	
	/*
	 * To get the real size used by a rbd image
	 */
	public String usedImg(String pool,String name) throws  Exception{
		nullCheck(executor);
		String res = usage(pool,name) ;
		return res;
	}
	
	/**
     * Get a RBD image's usage 
     * @param pool,image
     * @throws RbdException
     * @return the real size used by a rbd image
	 * @author Johnny Du
     */
	public String usage(String pool,String imageName) {
		String shell = "rbd diff " + pool + "/" + imageName + " | awk '{ SUM += $2 } END { print SUM/1024/1024 }'";
		String[] command = new String[]{ "/bin/sh", "-c", shell};
		String result;
		try {  
            Process ps = Runtime.getRuntime().exec(command);  
            BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));  
            StringBuffer sb = new StringBuffer();  
            String line;  
            while ((line = br.readLine()) != null) {  
                sb.append(line);  
            }  
            result = sb.toString();  
            return result;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
    }
	
//	public void deleteImg(String name)throws  Exception{
//		nullCheck(executor);
//		executor.remove(name);
//	}
	
	/**
     * Remove a RBD image 
     * @param pool,imageName
     * @throws Exception
     * @return the result of remove a rbd image
     * @author Johnny Du
     */
	public String deleteImg(String pool, String imageName) throws  Exception{
		String shell = "rbd remove " + pool + "/" + imageName + " 2>&1";
		String[] command = new String[]{ "/bin/sh", "-c", shell};
		String result;
		//boolean isMatch = false;
		try {  
            Process ps = Runtime.getRuntime().exec(command);  
            BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));  
            StringBuffer sb = new StringBuffer();  
            String line;  
            while ((line = br.readLine()) != null) {  
                sb.append(line); 
                sb.append("&&&");
            }  
            result = sb.toString();
            String[] res = result.split("&&&");
	        for (int q = 0 ;q < res.length ;q++) {
	        		if (res[q].contains("error:")) {
	        			return res[q];
	         	}
	        }
	        return "success";
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
	}
	
	public boolean isImageExisting(String imageName) throws Exception {
		return isImageExisting(imageName, true);
	}

	public boolean isImageExisting(String imageName, boolean isThrowEx) throws Exception {
		util.nullCheck(this.executor);
		RbdImage image = null;
		try {
			if ((null == imageName) || ("".equals(imageName))) {
				throw new CephWebException("No image name assigned");
		    }
		    image = this.executor.openReadOnly(imageName);
		    if (null == image) {
		        boolean i = false;
		        return i;
		    }
		    if (this.logger.isDebugEnabled()) {
		        this.logger.debug("information of image :" + JSONObject.toJSONString(image.stat()));
		    }
		    boolean i = true;
		    return i;
		} catch (Exception e) {
			this.logger.error("Can not get the image of " + imageName, e);
		    if (isThrowEx) {
		        throw new CephWebException(e.getMessage());
		    }
		    boolean i = false;
		    return i;
		} finally {
			if (null != image)
				try {
					this.executor.close(image);
		        } catch (Exception e) {
		        	this.logger.error("Can not close the image of " + imageName, e);
		    } 
		}
	}
	
	public void renameImg(String srcName,String destName) throws Exception{
		nullCheck(executor);
		executor.rename(srcName, destName);
	}
	
	public void release(){
		if( null != this.rados && null != ioCtx ){
			try {
				rados.getRados().ioCtxDestroy( this.ioCtx );
			} catch (Exception e) {
				 logger.error("Can not release the io context",e);
			}
		}
	}
	
	public String cephHealth() throws Exception{
		String shell = "ceph -s";
		String[] command = new String[]{ "/bin/sh", "-c", shell};
		String result;
		try {  
            Process ps = Runtime.getRuntime().exec(command);  
            BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));  
            StringBuffer sb = new StringBuffer();  
            String line;  
            while ((line = br.readLine()) != null) {  
                sb.append(line);  
            }  
            result = sb.toString();  
            return result;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
	}
	
	public void resize(String pool,String name,long newSize) throws Exception {
		nullCheck(executor);
		RbdImage image = executor.open(name);
		logger.debug("Now, we will resize the image :"+image.getName());
		executor.resize(image, newSize) ;
	}
	
	public String resizeImg(String pool,String name,long newSize) throws Exception{
//        String shell = "rbd resize --size " + newSize + " " + pool + "/" + name + " --allow-shrink";
		String shell = "/etc/ceph/resizeImg.sh "+"\""+newSize+"\" "+"\""+pool+"\" "+"\""+name+"\"";
        String result;
        try {  
          	result = executeShell(shell); 
            return result;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
    }
	
	public String resizeDir(String pool,String name) throws Exception{
        String shell = "rbd map " + pool + "/" + name ;
        String result;
        try {  
          	result = executeShell(shell); 
          	String resa = e2fsck(result); 
          	String resb = resize2fs(result);
          	String resc = unmap(result);
            return resc;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
    }
	
	public String e2fsck(String mountDir) {
		String shell = "e2fsck -f " + mountDir ;
        String result;
        try {  
        		result = executeShell(shell); 
            return result;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
	}
	
	public String resize2fs(String mountDir) {
		String shell = "resize2fs " + mountDir ;
        String result;
        try {  
        		result = executeShell(shell); 
            return result;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
	}
	
	public String unmap(String mountDir) {
		String shell = "rbd unmap " + mountDir ;
        String result;
        try {  
        		result = executeShell(shell); 
            return result;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
	}

	public CephRados getRados() {
		return rados;
	}

	public void setRados(CephRados rados) {
		this.rados = rados;
	}
	
	
	public String executeShell(String shell) throws Exception {
		logger.debug("the shell of executeShell : shell==="+shell);
        String[] command = new String[]{ "/bin/sh", "-c", shell};
        String result;
        try {  
            Process ps = Runtime.getRuntime().exec(command);  
            BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));  
            StringBuffer sb = new StringBuffer();  
            String line;  
            while ((line = br.readLine()) != null) {  
                sb.append(line);  
            }  
            result = sb.toString(); 
            logger.debug("the result of executeShell : result==="+result);
            return result;
        } catch (Exception e) {  
            e.printStackTrace();  
            return e.toString();
        }
	}
}
